﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace AVLTutorial
{
    public partial class MainPage : UserControl
    {
        // the main binary tree we will be representing
        public BinaryTreeNode binaryTree;

        // a wrapper for the actual AVL handling code, which is written in F#
        AVLTreeWrapper avlTree;

        // holds the substeps we are planning on doing
        System.Collections.Generic.IEnumerator<BinaryTreeNode> stepIterator;

        // the list of visual nodes we use to display the tree
        public List<VisualNode> visualNode = new List<VisualNode>();

        // the overall scaling factor of the tree
        public ScaleTransform masterScale = new ScaleTransform();

        // where the root of the tree is
        // all nodes are positioned relative to this
        public TranslateTransform rootPositon = new TranslateTransform();

        // the scale masterScale is being interpolated towards
        double wantedScale = 1;

        public double WantedScale { get { return wantedScale; } set { wantedScale = value; } }

        // used for the FPS counter
        int framesLastSecond = 0;
        int lastSecond = 0;

        // the number of miliseconds to wait after finishing a move
        const int WAIT_TIME = 500;

        public int treeHeight = 1;

        public MainPage()
        {
            InitializeComponent();

            // create the main tree for the first time
            avlTree = new AVLTreeWrapper(50);

            stepIterator = avlTree.InstructiveAdd(50).GetEnumerator();
            beginTreeAnimation();

            // set the starting root positions
            // note that every time the tree is revisualized, the center will be set to the actual center of the window
            // during this loading phase, the size of the silverlight plugin is unknown, so we can't query it here
            rootPositon.X = 500;
            rootPositon.Y = 50;

            // the main scaling factors applied to the tree
            masterScale.ScaleX = 1;
            masterScale.ScaleY = 1;

            // initialize the time sychronization
            lastFrame = DateTime.Now;

            // add the event so it is called when the window is rendered
            CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);

            // add a handler for when the plugin's size is changed
            this.SizeChanged += new SizeChangedEventHandler(MainPage_SizeChanged);

            //bool b1 = isIntegerString("-520");
            //bool b2 = isIntegerString("250");
            //bool b3 = isIntegerString("z9300g");
        }

        /// <summary>
        /// This detects when the plugin is resized and re-visaulizes the tree
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>

        void MainPage_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            visualizeMainTree();
        }

        // the exact time of the last frame
        DateTime lastFrame;

        void initMoveModifiers()
        {
            foreach (VisualNode visual in visualNode)
                visual.setMovementModifier(this);
        }

        DateTime lastMove;

        void setAllButtonsEnabled(bool state)
        {
            foreach (UIElement ui in LayoutRoot.Children)
            {
                if (ui.GetType() == typeof(Button))
                {
                    // there is one case where we DO NOT want to do this
                    // this setting the substep button to enabled will break its whole logic
                    if(state == false || ((Button)ui).Name != "NextStepButton")
                        ((Button)ui).IsEnabled = state;
                }
            }
        }

        /// <summary>
        /// Called every frame to render the tree's new position.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        
        void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            // place the root
            // this may have changed because the plugin was resized, or because we cannot initialize this in the constructor
            rootPositon.X = this.ActualWidth / 2;

            // calculate how long its been since the last graphics update
            double timeSinceLastFrame = (DateTime.Now - lastFrame).Ticks / 10000000.0;
            lastFrame = DateTime.Now;

            wantedScale = 1;

            // move all the nodes
            foreach (VisualNode visual in visualNode)
            {
                //if (
                visual.update(this, LayoutRoot, timeSinceLastFrame);//) lastMove = DateTime.Now;
            }

            // interpolate masterScale towards wantedScale
            masterScale.ScaleX = masterScale.ScaleX * 0.92 + wantedScale * 0.08;
            masterScale.ScaleY = masterScale.ScaleY * 0.92 + wantedScale * 0.08;

            // for tracking the FPS
            framesLastSecond++;

            if (DateTime.Now.Second != lastSecond)
            {
                lastSecond = DateTime.Now.Second;
                FPSDisplay.Content = "FPS: " + framesLastSecond;
                framesLastSecond = 0;
            }

            if (lastMove.AddMilliseconds(SpeedSlider.Value*1000 + WAIT_TIME) < DateTime.Now &&
                manualStepOn.IsChecked == false)
            {
                visualizeMainTree();
            }

            if (lastMove.AddMilliseconds(SpeedSlider.Value * 1000 + WAIT_TIME + 150) < DateTime.Now)
                setAllButtonsEnabled(true);
            else
                setAllButtonsEnabled(false);

            setNextStepButtonEnabled();
        }

        void setNextStepButtonEnabled()
        {
            NextStepButton.IsEnabled = subStepAvailable && manualStepOn.IsChecked == true;
        }

        /// <summary>
        /// Locates a VisualNode in the main visual node list with the specified integer data.
        /// </summary>
        /// <param name="data">The integer data we are looking for.</param>
        /// <returns>The found node, or null if there is no such node.</returns>
        
        public VisualNode findVisualNode(int data)
        {
            for (int i = 0; i < visualNode.Count; i++)
            {
                if (visualNode[i].Data == data) return visualNode[i];
            }

            return null;
        }

        void findVeerAmounts()
        {
            foreach (VisualNode visual in visualNode)
                visual.setVeerAmount(this);
        }

        bool subStepAvailable = false;

        // performs all the steps needed to show the main tree
        void visualizeMainTree()
        {
            // if there are no substeps, there is nothing to do here
            if (!subStepAvailable) return;

            // get the data waiting in the iterator
            binaryTree = stepIterator.Current;

            // calculate the depth, and update each node's depth information
            int depth;

            // account for the case where the tree is empty
            if(binaryTree != null)
                depth = binaryTree.getMaxDepth(0, -1);
            else
                depth = -1;

            treeHeight = Math.Max(treeHeight, depth);

            foreach (VisualNode visual in visualNode) visual.isInUse = false;

            // recursivley go through the tree and display each node
            // be sure to NOT do this if the tree is empty, or we'll get an exception
            if(binaryTree != null) binaryTree.visualize(this, LayoutRoot, treeHeight, 0, 0, -1, -1, -1);

            for (int i = 0; i < visualNode.Count; i++)
            {
                if (visualNode[i].isInUse == false)
                {
                    visualNode[i].destroy(LayoutRoot);
                    visualNode.RemoveAt(i);
                    i--;
                }
            }

            initMoveModifiers();

            findVeerAmounts();

            nextSubStep();
        }

        private void addButton_Click(object sender, RoutedEventArgs e)
        {
            NUMBER_STRING_TYPES resultCode = getNumberType(newNodeValue.Text);
            if (resultCode != NUMBER_STRING_TYPES.IN_RANGE)
                MessageBox.Show(numberParseErrorToString(resultCode));
            else
            {
                int data = int.Parse(newNodeValue.Text);
                if (findVisualNode(data) == null)
                {
                    insertNode(data);
                    newNodeValue.Text = "";
                }
                else
                    MessageBox.Show(string.Format("Node {0} already exists in the tree. Nodes must have unique data.", data));
            }
        }

        private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
        {
            // generate the visuals for the first time
            visualizeMainTree();
        }

        private void addRandomButton_Click(object sender, RoutedEventArgs e)
        {
            // generate a new random seed
            Random rand = new Random();

            int newNodeData = 50;

            int i;

            for (i = 0; i < 1000000; i++)
            {
                newNodeData = rand.Next(100);
                if (findVisualNode(newNodeData) == null) break;
            }

            if(i == 1000000){
                MessageBox.Show("The tree is full!");
            }

            // add the random node
            insertNode(newNodeData);
        }

        void nextSubStep()
        {
            subStepAvailable = stepIterator.MoveNext();

            setNextStepButtonEnabled();
        }

        void beginTreeAnimation()
        {
            lastMove = DateTime.Now;

            treeHeight = 0;

            nextSubStep();
        }

        void insertNode(int nodeData)
        {
            // add the node
            stepIterator = avlTree.InstructiveAdd(nodeData).GetEnumerator();

            beginTreeAnimation();

            // display the result
            visualizeMainTree();
        }

        void removeNode(int nodeData)
        {
            // add the node
            stepIterator = avlTree.InstructiveRemove(nodeData).GetEnumerator();

            beginTreeAnimation();

            // display the result
            visualizeMainTree();
        }

        private void removeButton_Click(object sender, RoutedEventArgs e)
        {
            NUMBER_STRING_TYPES resultCode = getNumberType(newNodeValue.Text);
            if (resultCode != NUMBER_STRING_TYPES.IN_RANGE)
                MessageBox.Show(numberParseErrorToString(resultCode));
            else
            {
                int data = int.Parse(newNodeValue.Text);
                if (findVisualNode(data) != null)
                {
                    removeNode(data);
                    newNodeValue.Text = "";
                }
                else
                    MessageBox.Show(string.Format("Node {0} does not exist. Please choose another one for deletion.", data));
            }
        }

        private void nextStep_Click(object sender, RoutedEventArgs e)
        {
            visualizeMainTree();
        }
        /// <summary>
        /// Figures out if the entered string is an integer
        /// </summary>
        /// <param name="str">The string to be processed</param>
        /// <returns></returns>

        bool isIntegerString(string str)
        {
            str = str.Trim();
            for (int i = 0; i < str.Length; i++)
            {
                if (str[i] == '-' && i != 0)
                    return false;
                else if (i == 0 && str[i] == '-') continue;

                if (str[i] < '0' || str[i] > '9') return false;
            }

            return true;
        }

        enum NUMBER_STRING_TYPES
        {
            NOT_AN_INTEGER,
            MULTIPLE_NOT_INTEGER,
            MULTIPLE,
            OUT_OF_RANGE,
            IN_RANGE
        }

        /// <summary>
        /// Takes the number entered by the user, and categorizes it into one of 5 categories
        /// NOT_AN_INTEGER - They entered one thing, but its not an integer at all
        /// MULTIPLE_NOT_INTEGER - The user entered multiple strings, but one or more weren't integers
        /// MULTIPLE - The user entered multiple integers
        /// OUT_OF_RANGE - The user entered one integer, but it was out of the bounds of a 32 bit integer
        /// IN_RANGE - The number is in bounds and is an integer
        /// </summary>
        /// <param name="str">The string to be processed.</param>
        /// <returns></returns>

        NUMBER_STRING_TYPES getNumberType(string str)
        {
            string[] strings = str.Split(' ');

            if (strings.Length > 1)
            {
                bool allAreIntegers = true;
                foreach (string s in strings)
                {
                    if (!isIntegerString(s)) allAreIntegers = false;
                }
                if (allAreIntegers)
                {
                    return NUMBER_STRING_TYPES.MULTIPLE;
                }
                else
                {
                    return NUMBER_STRING_TYPES.MULTIPLE_NOT_INTEGER;
                }
            }

            int number = 0;

            if (!isIntegerString(str)) return NUMBER_STRING_TYPES.NOT_AN_INTEGER;
            if (!int.TryParse(str, out number)) return NUMBER_STRING_TYPES.OUT_OF_RANGE;

            return NUMBER_STRING_TYPES.IN_RANGE;
        }

        /// <summary>
        /// Converts an error code from the string categorizer into a useful string.
        /// </summary>
        /// <param name="error"></param>
        /// <returns></returns>

        string numberParseErrorToString(NUMBER_STRING_TYPES error)
        {
            switch (error)
            {
                case NUMBER_STRING_TYPES.MULTIPLE: return "Please only enter one integer value at a time.";
                case NUMBER_STRING_TYPES.MULTIPLE_NOT_INTEGER: return "Please enter only one integer value at a time.";
                case NUMBER_STRING_TYPES.OUT_OF_RANGE: return string.Format("Entered values must be between {0} and {1}.", int.MinValue, int.MaxValue);
                case NUMBER_STRING_TYPES.NOT_AN_INTEGER: return "Please only enter integer values for node data.";
            }
            return null;
        }

        private void SpeedSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if(animationSpeedLabel != null)
                animationSpeedLabel.Content = string.Format("Animation Speed ({0:F}s)", SpeedSlider.Value + WAIT_TIME / 1000.0);
        }
    }
}
